function bciSignalGenerator(nChan,srate,param)

% generate sine wave signal for some channels
% bciSignalGenerator(nChan,param)
% param.method (0 for mouse pointer position, 1 for SSVEP,2 for P300)
% param.mouseXChan
% param.mouseYChan
% param.amplitudeInChans
% param.SNRdB
% param.freqsInChans
% param.offsetInChans
% param.blockSize
% param.duration;
% param.IP


if nargin<3,
    param=struct;
end

if ~isfield(param,'method'),
    param.method = 0;
end
if ~isfield(param,'mouseXChan'),
    param.mouseXChan = 1;
end
if ~isfield(param,'mouseYChan'),
    param.mouseYChan = 2;
end
if ~isfield(param,'SSVEPChans'), % also used for P300
    param.SSVEPChans = 1:5;
end
if ~isfield(param,'SSVEPFreqs'),
    param.SSVEPFreqs = [60/9 60/7 10 15];
end
if ~isfield(param,'SSVEPDirs'),
    param.SSVEPDirs = [90 -90 180 0]; % direction of targets; use polar coordinate conversion
end
if ~isfield(param,'amplitudeInChans'),
    param.amplitudeInChans = ones(1,nChan);
end
if ~isfield(param,'SNRdB'),
    param.SNRdB = 0;
end
if ~isfield(param,'freqsInChans'),
    param.freqsInChans = 1:nChan;
end
if ~isfield(param,'offsetInChans'),
    param.offsetInChans = 1:nChan;
end
if ~isfield(param,'blockSize'),
    param.blockSize = 0.1;
end
if ~isfield(param,'duration'),
    param.duration = 300; 
end
if ~isfield(param,'IP'),
    param.IP = '127.0.0.1';
end

filename = ['buffer://' param.IP ':1972'];
hdr.Fs = srate;
hdr.nChans = nChan;

if exist('ft_write_data','file'),
    writeFunc=str2func('ft_write_data');
elseif exist('write_data','file'),
    writeFunc=str2func('write_data');    
else
    error('No write_data function on path.');
end
% some output for the user 
disp(['running for: ' num2str(param.duration) ' s']);
% write header data to buffer

writeFunc(filename,[],'header',hdr);

nSamp = round(param.blockSize*srate);

time=linspace(0,param.duration,round(param.duration*srate));
tIdx = 0;
dT=diff(time(1:2));
param.freqsInChans = param.freqsInChans*2*pi;
screenSize = get(0,'screensize');
screenSize = screenSize(3:4);

% std of sin signal 
stdSin = sin(pi/4);
snr=10^(param.SNRdB/10);

% mat initialization
ampInChanMat = param.amplitudeInChans'*ones(1,nSamp);
freqInChanMat = param.freqsInChans'*ones(1,nSamp);
offsetInChanMat = param.offsetInChans'*ones(1,nSamp);

% SSVEP specific initialization
if param.method==1,
    param.SSVEPFreqs = param.SSVEPFreqs*2*pi;
    screenCenter = screenSize./2;
    screenRadius = min(screenCenter);
    maxDist = sqrt((screenSize(1)/2)^2+screenSize(2)^2);
    [targetPosX, targetPosY] = pol2cart(param.SSVEPDirs*pi/180,...
                               ones(1,length(param.SSVEPDirs))*screenRadius);
	targetPosX = targetPosX+screenCenter(1);
    targetPosY = targetPosY+screenCenter(2);
    chanWeights = ones(length(param.SSVEPChans),length(param.SSVEPFreqs))./length(param.SSVEPChans);
    chanWeights(1,:) = 1; % assume first channel as center channel
end

% P300 specific initialization
if param.method==2,
    fH=figure;
    fpos = get(fH,'position');
    alignment = round([fpos(3)*0.1 fpos(4)*0.3 fpos(3)*0.8 fpos(4)*0.3]);
    % define a button than resets the matlab timer (tic) on click
    tic;
    pause(0.3);
    buttonH=uicontrol(fH,'Style', 'pushbutton', 'String', 'Send Potential',...
                        'Position', alignment,'Callback', 'tic');
    % different strength in channels
    chanWeights = (1:length(param.SSVEPChans))./param.amplitudeInChans(param.SSVEPChans)./length(param.SSVEPChans);
    % ERP template of length 100 ms
    erpLength = 0.1;
    erpLengthSamp = round(srate*erpLength);
    erpTemplate = 2*(gausswin(erpLengthSamp)*chanWeights)';
    erpOnset = -inf;
    sendedERPsamp=inf;
end

% start sending data loop
startTime = GetSecs;
while tIdx+nSamp<length(time),
    % generate data
    idx = tIdx+1:tIdx+nSamp;
    timeMat = ones(nChan,1)*time(idx);
    % get mouse position
    mousePos = get(0,'pointerlocation');
    % default sine signal
    sineMat = sin(freqInChanMat.*timeMat);
    if param.method==0,
        % modulated sine signal
        sineMat(param.mouseXChan,:) = sineMat(param.mouseXChan,:).*mousePos(1)./screenSize(1);
        sineMat(param.mouseYChan,:) = sineMat(param.mouseYChan,:).*mousePos(2)./screenSize(2);
    elseif param.method==1,
        % modulated sine signal
        distToTarget = sqrt((targetPosX-mousePos(1)).^2+(targetPosY-mousePos(2)).^2);
        W = chanWeights.*(ones(length(param.SSVEPChans),1)*(1-distToTarget/maxDist));
        sineMat(param.SSVEPChans,:) = W.^2*sin(param.SSVEPFreqs'*time(idx));
    elseif param.method==2,
        % modulated sine signal
        buttonPressDelay = toc;
        if buttonPressDelay < min(param.blockSize,0.3)&&sendedERPsamp>=erpLengthSamp,
            fprintf('send delay: %2.3f s\n',buttonPressDelay);              % add artificial ERP if button was pressed
            erpOnset = timeMat(1,1);% + max(0.1-buttonPressDelay,0);
            templateTime = linspace(erpOnset,erpOnset+erpLength,erpLengthSamp);
            sendedERPsamp=0;
        end
        if timeMat(1,1) < erpOnset + erpLength,
        blockMatBegin = find(timeMat(1,:) >= erpOnset,1);
            templateBegin = find(templateTime >= timeMat(1,1)-dT,1);
            if isempty(blockMatBegin),
                blockMatBegin = nSamp+1;
            end
            blockMatEnd = min(nSamp,blockMatBegin + erpLengthSamp - sendedERPsamp-1);
            templateEnd = templateBegin + blockMatEnd - blockMatBegin;
            if isempty(templateBegin),
                templateBegin = erpLengthSamp+1;
            end
            blockMatIdx = blockMatBegin:blockMatEnd;
            templateIdx = templateBegin:templateEnd;
            sendedERPsamp=sendedERPsamp + length(templateIdx);
            sineMat(param.SSVEPChans,blockMatIdx) = sineMat(param.SSVEPChans,blockMatIdx) + erpTemplate(:,templateIdx);
        end
    else
        error('param.method is unknown.');
    end
    % calculate the signal:  (noise + signal)*amplitude + offset
    dat = ((randn(nChan,nSamp).*stdSin./snr)+sineMat).*ampInChanMat + offsetInChanMat;
    % wait for time point to send the data
    while GetSecs-startTime<time(idx(end)),
        pause(1/srate);
    end
    writeFunc(filename,dat);
    tIdx=tIdx+nSamp;
end
% some output for the user 
disp(['Time is over']);
